/*
 Linux Driver for Enterpoint's Raggedstone1 FPGA PCI Board with lcpd-scope core.
 This driver allows you, to add the PCI card to your computer and use it as an oszilloscope.

 Written by T.Kurmann, R. Woodtli, S. Eichenberger <kurmt1_AT_bfh.ch,
 woodr1_AT_bfh.ch, eichs2_AT_bfh.ch>
 Based on examples from the Linux Kernel Module Programming Guide and a example
 of Manuel Bessler <m.bessler_AT_gmx.net>
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or 
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef MODULE
#define MODULE
#endif

#include <linux/autoconf.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/version.h>             /* >= 2.6.14 LINUX_VERSION_CODE */
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/cdev.h>	

#include <asm/uaccess.h>

#include "lcpd_scope_driver.h"
#include "lcpd_scope_user.h"

unsigned long memstart = 0, memlen = 0;
void * vaddr = 0;
static int Device_Open = 0;
u8 read_flag;
static DECLARE_WAIT_QUEUE_HEAD( read_queue);
u32 DataBuffer[4000];

// Struct to register file operation in the driver
struct file_operations Fops =
{
		open: device_open,
		read: device_read,
		release: device_close,
		ioctl: device_ioctl
};

// Strings to identify the PCI-Card
static struct pci_device_id pci_device_id_DevicePCI[] =
{
{ VENDOR_ID_ALTERA, DEVICE_ID_ALTERA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
{ VENDOR_ID_XILINX, DEVICE_ID_XILINX, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
{ } /* end of list */
};

// Struct to register PCI Device in the driver
struct pci_driver pci_driver_DevicePCI =
{
		name: "lcpd-sope",
		id_table: pci_device_id_DevicePCI,
		probe: device_probe,
		remove: device_remove
};

/**
 * \brief  	PCI Interrupt
 *
 *			this interrupt triggered when the FPGA buffered enugh data for read
 *			then he wakes up read.
 *
 * \param	irq   	irq number
 * \param	*dev_id	device id
 * \param	pt_regs pt registers
 *
 * \return	      	Success or not
 *
 */
irqreturn_t device_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	if (read_flag == 0)
	{
		read_flag = 1;
#ifdef DEBUG
			printk(KERN_INFO "lcpd-sope: wake up read! %s\n",(char*)dev_id);
#endif
		// wake up read from sleep
		wake_up_interruptible(&read_queue);
	}
	return IRQ_HANDLED;
}

/**
 * \brief  	Device Read
 *
 *			this function called at a device read,
 *			it sends the buffer size to the fpga and
 *			let the fpga buffering data.
 *
 * \param	file*  	file pointer
 * \param	size_t	data size
 * \param	loff_t*	ppos
 *
 * \return	      	Success or not
 *
 */
static ssize_t device_read(struct file *file, char *buf, size_t size, loff_t *ppos)
{
	spinlock_t read_lock = SPIN_LOCK_UNLOCKED;
	u32 ret;
	u32 flags;
	u32 i;
#ifdef DEBUG
	printk(KERN_INFO "lcpd-sope: Read size: %d",size);
#endif
	if (size <= 4000)
	{
		iowrite32(size, vaddr + BUFFER_SIZE_ADDR); //Write Size to the Size register and start the fpga to buffer
		iowrite32(GPIO_ON, vaddr + TRIGGER_ADDR); //Write Start to GPIOs
		// Spin lock to make read_flag irq save
		spin_lock_irqsave(&read_lock, flags);
		read_flag = 0;
		spin_unlock_irqrestore(&read_lock, flags);
#ifdef DEBUG
		printk(KERN_INFO "lcpd-sope: read going to sleep");
#endif
		ret = wait_event_interruptible_timeout(read_queue, read_flag, 5 * HZ); // Put read function in sleep mode

		if (!ret)
		{
			printk(KERN_INFO "lcpd-sope: Error read timeouted");
			iowrite32(GPIO_OFF,vaddr+TRIGGER_ADDR); //Write Start to GPIOs
			return READ_ERROR_TIMEOUT;
		}
		else
#ifdef DEBUG
			printk(KERN_INFO "lcpd-sope: read waked up");
#endif

		//memcpy_fromio(vaddr+BASE_OFFSET_DATA,DataBuffer,size);
		for (i = 0; i < size; i++)
		{
			//transfer data from fpga to ram
			DataBuffer[i] = ioread32(vaddr + BASE_OFFSET_DATA + (i << 2));
			//printk(KERN_INFO "%d ",DataBuffer[i]);
		}
		iowrite32(GPIO_OFF, vaddr + TRIGGER_ADDR); //Write Start to GPIOs
		//copy data to userspace
		copy_to_user(buf, DataBuffer, size * sizeof(int));

	}
	else
		return READ_ERROR_BUFFER_SIZE;
	return 0;

}

/**
 * \brief  	Read SPI
 *
 *			read data from the spi buffer of the fpga
 *
 * \param	char  	SPI Adress
 *
 * \return	      	SPI Data
 *
 */
char read_spi(char cSpiAddr)
{
	u32 cSpiData;
	iowrite32(GPIO_OFF, vaddr + SPI_ADDR_CS);
	iowrite32(cSpiAddr, vaddr + ADDRSPISPDR);
	cSpiData = ioread32(vaddr + ADDRSPISPDR);
	iowrite32(GPIO_ON, vaddr + SPI_ADDR_CS);
	return cSpiData;
}
/**
 * \brief  	write spi data
 *
 *			write data to the spi buffer of the fpga
 *
 * \param	char	SPI Data
 * 			char	SPI Adress
 *
 * \return	      	Success or not
 *
 */
int write_spi(char cSpiData, char cSpiAddr)
{
	int i;
	int spsr = 0;
	// Chip select to zero
	iowrite32(GPIO_OFF, vaddr + SPI_ADDR_CS);
	// first 8 bit are the address the second 8 the data the protocol is 16 bit orientated
	iowrite32(cSpiAddr, vaddr + ADDRSPISPDR);
	iowrite32(cSpiData, vaddr + ADDRSPISPDR);
	// wait for data sent flag or timeout
	for (i = 0; (i < 200) && !(spsr & D2); i++)
	{
		spsr = ioread32(vaddr + ADDRSPISPSR);
	}
	// Chip select to one
	iowrite32(GPIO_ON, vaddr + SPI_ADDR_CS);
	return SUCCESS;
}
/**
 * \brief  	init spi
 *
 *			init the spi interface of the fpga
 *
 * \return	      	Success or not
 *
 */
int init_spi(void)
{
	// SPCR D7 = 0 Interrupt disabled
	// SPCR D6 = 1 SPI core enabled
	// SPCR D5 = 1 Reserved
	// SPCR D4 = 1 Master mode
	// SPCR D3 = 1 falling Clk
	// SPCR D2 = 1 falling CLK-Phase
	// SPCR D1 & D0 = 11 Divide WISCHBONE clock by 32
	char cSPCR = 0x7C;
	iowrite32(cSPCR, vaddr + ADDRSPISPCR);
	iowrite32(GPIO_ON, vaddr + SPI_ADDR_CS);
	printk(KERN_INFO "lcpd-sope: SPI initialized\n");
	return SUCCESS;
}
/**
 * \brief  	init A/D Converter
 *
 *			initialize the A/D Converter over SPI
 *
 * \return	      	Success or not
 *
 */
int init_ADConverter(void)
{
	write_spi(SPI_RESET, 0X00); // Reset AD-Wandler	
	write_spi(SPI_INTREF, 0x3F); // AD-Wandler auf Interne Ref.Spannung stellen
	write_spi(SPI_LVDS, 0x41); // AD-Wandler auf LVDS stellen
	write_spi(SPI_2S_COMP, 0x50); // Daten-Format auf 2er Komplement stellen
	printk(KERN_INFO "lcpd-sope: A/D Converter initalized\n");
	return SUCCESS;
}
/**
 * \brief  	ioctl of the device
 *
 *			configure the settings of the fpga, A/D converter, etc.
 *			possible parameters (ioctl_num):\n
 *			IOCTL_SETSAMPLERATE\n
 *			IOCTL_SETBUFFERSIZE\n
 *			IOCTL_SETTRIGLEV_A\n
 *			IOCTL_SETVOLTLEV_A\n
 *			IOCTL_SETTRIGLEV_B\n
 *			IOCTL_SETVOLTLEV_B\n
 *			A/D converter settings\n
 *			IOCTL_ADRESET\n
 *			IOCTL_ADSPEED\n
 *			IOCTL_ADREF\n
 *			IOCTL_ADSTANDBY\n
 *			IOCTL_ADPWDMODE\n
 *			IOCTL_ADLVDSCOMOS\n
 *			IOCTL_ADDATAFORMAT\n
 *			IOCTL_ADOFFCORR_A\n
 *			IOCTL_ADOFFCORR_B\n
 *			IOCTL_ADTSTPATTERN_A\n
 *			IOCTL_ADTSTPATTERN_B\n
 *
 * \param	inode  	Pointer that representing a file
 * \param	int		Number of the operation
 * \param	long	Argument (Value, etc.)
 *
 * \return	      	Success or not
 *
 */
int device_ioctl(struct inode *inode, struct file *file, unsigned int ioctl_num, unsigned long ioctl_param)
{
#ifdef DEBUG
	printk (KERN_INFO "lcpd-sope: device_ioctl(%p,%p,ioctl_param=0x%x)\n", inode, file, (u16)ioctl_param);
#endif
	switch (ioctl_num)
	{
	//set sample rate
	case IOCTL_SETSAMPLERATE:
		if ((ioctl_param <= MAX_SAMPLING_RATE) & (ioctl_param
				>= MIN_SAMPLING_RATE))
			iowrite32(ioctl_param, vaddr + ADDRSAMPLERATE);
		else
			return IOCTL_ERROR_SAMPLERATE;
		break;
	//set buffer size
	case IOCTL_SETBUFFERSIZE:
		if ((ioctl_param <= MAX_BUFFER_SIZE) & (ioctl_param >= MIN_BUFFER_SIZE))
			iowrite32(ioctl_param, vaddr + ADDRBUFFERSIZE);
		else
			return IOCTL_ERROR_BUFFERSIZE;
		break;
	//set trigerlevel
	case IOCTL_SETTRIGLEV_A:
		if (ioctl_param < 0xFFF)
			iowrite32(ioctl_param, vaddr + ADDRCHA + ADDRTRIGGERLEV);
		else
			return IOCTL_ERROR_TRIGGER_LEVEL;
		break;
	//set voltage level of channel A
	case IOCTL_SETVOLTLEV_A:
		if (ioctl_param <= 3)
			iowrite32(ioctl_param, vaddr + ADDRCHA + ADDRVOLTAGELEV);
		else
			return IOCTL_ERROR_VOLTAGELEVEL;
		break;
	//set trigger level of channel B
	case IOCTL_SETTRIGLEV_B:
		if (ioctl_param < 0xFFF)
			iowrite32(ioctl_param, vaddr + ADDRCHB + ADDRTRIGGERLEV);
		else
			return IOCTL_ERROR_TRIGGER_LEVEL;
		break;
	//set voltage lebel of channel B
	case IOCTL_SETVOLTLEV_B:
		if (ioctl_param <= 3)
			iowrite32(ioctl_param, vaddr + ADDRCHB + ADDRVOLTAGELEV);
		else
			return IOCTL_ERROR_VOLTAGELEVEL;
		break;
	//reset A/D converter
	case IOCTL_ADRESET:
		if (ioctl_param == SPI_RESET)
			write_spi(ioctl_param, 0x00);
		else
			return IOCTL_ERROR_RESET;
		break;
	//set A/D converter speed level
	case IOCTL_ADSPEED:
		if (ioctl_param == SPI_LOW_SPEED | ~(SPI_LOW_SPEED))
			write_spi(ioctl_param, 0x20);
		else
			return IOCTL_ERROR_RESET;
		break;
	//set A/D referece mode
	case IOCTL_ADREF:
		if (ioctl_param == (SPI_EXTREF | SPI_INTREF))
		{
			char temp = read_spi(0x3F) & ~(ioctl_param); // save the other settings
			write_spi(temp, 0x3F);
		}
		else
			return IOCTL_ERROR_REF;
		break;
	//set A/D converter to standby
	case IOCTL_ADSTANDBY:
		if (ioctl_param == (SPI_STANDBY | ~(SPI_STANDBY)))
		{
			char temp = read_spi(0x3F) & ~(ioctl_param); // save the other settings
			write_spi(temp, 0x3F);
		}
		else
			return IOCTL_ERROR_STANDBY;
		break;
	//set A/D power mode
	case IOCTL_ADPWDMODE:
		if (ioctl_param <= 0x0F)
			write_spi(ioctl_param, 0x40);
		else
			return IOCTL_ERROR_POWERDOWNMODE;
		break;
	//set A/D Protocol type (LVDS or CMOS)
	case IOCTL_ADLVDSCOMOS:
		if (ioctl_param = SPI_LVDS | ~(SPI_LVDS))
			write_spi(ioctl_param, 0x41);
		else
			return IOCTL_ERROR_LVDSCMOS;
		break;
	//set A/D data format
	case IOCTL_ADDATAFORMAT:
		if (ioctl_param == (SPI_2S_COMP | SPI_OFFSET_BIN))
			write_spi(ioctl_param, 0x50);
		else
			return IOCTL_ERROR_DATAFORMAT;
		break;
	//shut down A/D channel A
	case IOCTL_ADOFFCORR_A:
		if (ioctl_param == (SPI_ENABLE_OFFS_CORR | ~(SPI_ENABLE_OFFS_CORR)))
			write_spi(ioctl_param, 0x53);
		else
			return IOCTL_ERROR_OFFS_CORR;
		break;
	//shut down A/D channel B
	case IOCTL_ADOFFCORR_B:
		if (ioctl_param == (SPI_ENABLE_OFFS_CORR | ~(SPI_ENABLE_OFFS_CORR)))
			write_spi(ioctl_param, 0x66);
		else
			return IOCTL_ERROR_OFFS_CORR;
		break;
	//set A/D test pattern of channel A
	case IOCTL_ADTSTPATTERN_A:
		if (ioctl_param <= 0x07)
			write_spi(ioctl_param, 0x62);
		else
			return IOCTL_ERROR_TESTPATTERNS;
		break;
	//set A/D test pattern of channel B
	case IOCTL_ADTSTPATTERN_B:
		if (ioctl_param <= 0x07)
			write_spi(ioctl_param, 0x75);
		else
			return IOCTL_ERROR_TESTPATTERNS;
		break;
	}
	return SUCCESS;
}
/**
 * \brief  	open function of the device
 *
 *			This function is called whenever a process attempts
 * 			to open the device file
 *
 * \param	inode  	Pointer that representing a file
 * \param	file	Flags and settings of the file
 *
 * \return	      	Success or not
 *
 */
static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
	printk("lcpd-sope: device_open(%p)\n", file);
#endif
	//We don't want to talk to two processes at the
	//same time
	if (Device_Open)
		return -EBUSY;
	Device_Open++;
	return SUCCESS;
}
/**
 * \brief  	close function of the device
 *
 *			This function is called whenever a process attempts
 * 			to close the device file
 *
 * \param	inode  	Pointer that representing a file
 * \param	file	Flags and settings of the file
 *
 * \return	      	Success or not
 *
 */
static int device_close(struct inode *inode, struct file *file)
{
#ifdef DEBUG
	printk("device_release(%p,%p)\n", inode, file);
#endif
	Device_Open--;

	return 0;
}
/**
 * \brief  	probe function of the device
 *
 *			This function is called when the pci core think's he has found
 *			a pci device, that matchs with the struct pci_dev
 *
 * \param	pci_dev*		Pointer to the whole device structure
 * \param	pci_device_id*	ID that holds vendor and device id
 *
 * \return	      	Success or not
 *
 */
int device_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
	int ret;
	// enable the pci device
	ret = pci_enable_device(dev);
	if (ret < 0)
	{
		printk(KERN_WARNING "lcpd-sope: unable to initialize PCI device\n");
		return ret;
	}
	// try to reseve the pci regions
	ret = pci_request_regions(dev, "lcpd-scope");
	if (ret < 0)
	{
		printk(KERN_WARNING "lcpd-sope: unable to reserve PCI resources\n");
		pci_disable_device(dev);
		return ret;
	}
	//get start adress and len of the pci device (physical)
	memstart = pci_resource_start(dev, 0); /* 0 for BAR0 */
	memlen = pci_resource_len(dev, 0);
	printk(KERN_WARNING "lcpd-sope: memstart=0x%lx memlen=%li\n", memstart, memlen);
	//get virtual adress
	vaddr = ioremap(memstart, memlen);

	printk(KERN_INFO "lcpd-sope: device_probe successful\n");

	// Register the character device (atleast try)
	ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);

	// Negative values signify an error
	if (ret < 0)
	{
		printk (KERN_INFO "%s failed with %d\n",
				"Sorry, registering the character device ",
				ret);
		return ret;
	}
	else
	{
		printk	(KERN_INFO "%s The major device number is %d.\n",
				"Registeration is a success",
				MAJOR_NUM);
		printk (KERN_INFO "If you want to talk to the device driver,\n");
		printk (KERN_INFO "you'll have to create a device file. \n");
		printk (KERN_INFO "We suggest you use:\n");
		printk (KERN_INFO "mknod /dev/%s c %d 0\n", DEVICE_NAME,
				MAJOR_NUM);
		printk (KERN_INFO "The device file name is important, because\n");
		printk (KERN_INFO "the ioctl program assumes that's the\n");
		printk (KERN_INFO "file you'll use.\n");
	}

	//enable interrupt
	ret= request_irq(dev->irq,device_interrupt,IRQF_SHARED,"lcpd-scope","lcpd-scope");
	if (ret)
	{
		printk (KERN_INFO "%s failed with %d\n",
				"Error in enabling the interrupt",
				ret);
		return ret;
	}
	else
	{
		printk (KERN_INFO "the interrupt has succesfuly enabled");
	}

	// initialize SPI and A/C converter
	init_spi();
	init_ADConverter();
	return ret;
}
/**
 * \brief  	remove function of the device
 *
 *			Function will be called at loosing the pci device
 *
 * \param	pci_dev*		Pointer to the whole device structure
 *
 */
void device_remove(struct pci_dev *dev)
{

	unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
	free_irq(dev->irq, "lcpd-scope"); //free the irq
	iounmap(vaddr);
	/*  release_mem_region(memstart, memlen); */

	pci_release_regions(dev);
	pci_disable_device(dev);
	printk(KERN_INFO "lcpd-sope: device removed\n");
}
/**
 * \brief  	init function of the device
 *
 *			This function when the driver loads.
 *
 * \return	Success or not
 *
 */
static int init_module_DevicePCI(void)
{
	printk(KERN_INFO "lcpd-sope: init\n");
	return pci_register_driver(&pci_driver_DevicePCI);
}
/**
 * \brief  	cleaup function of the device
 *
 *			This function when the driver exits.
 *
 * \return	Success or not
 *
 */
void cleanup_module_DevicePCI(void)
{
	printk(KERN_INFO "lcpd-sope: cleanup\n");
	pci_unregister_driver(&pci_driver_DevicePCI);
}

//make init and exit known in the system
module_init( init_module_DevicePCI);
module_exit( cleanup_module_DevicePCI);

